package com.whcoding.test.common.file;


import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.whcoding.test.common.DateTimeUtils;
import com.whcoding.test.constant.DateStyle;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.*;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * @author Manager
 * @desc:文件工具类
 */
public class FileUtils {

	private static final Logger log = LoggerFactory.getLogger(FileUtils.class);
	private static final String FOLDER_SEPARATOR = "/";
	private static final char EXTENSION_SEPARATOR = '.';
	private static final int RANDOM_NUM = RandomUtil.randomInt(1000, 9999);
	/**
	 * 系统临时目录
	 * <br>
	 * windows 包含路径分割符，但Linux 不包含,
	 * 在windows \\==\ 前提下，
	 * 为安全起见 同意拼装 路径分割符，
	 * <pre>
	 *       java.io.tmpdir
	 *       windows : C:\Users/xxx\AppData\Local\Temp\
	 *       linux: /temp
	 * </pre>
	 */
	public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;

	/**
	 * 定义GB的计算常量
	 */
	private static final int GB = 1024 * 1024 * 1024;
	/**
	 * 定义MB的计算常量
	 */
	private static final int MB = 1024 * 1024;
	/**
	 * 定义KB的计算常量
	 */
	private static final int KB = 1024;
	/**
	 * 格式化小数
	 */
	private static final DecimalFormat DF = new DecimalFormat("0.00");

	/***
	 * File转化为MultipartFile
	 * @param file
	 * @return
	 */
	public static MultipartFile fileToMultipartFile(File file) {
		FileItem fileItem = createFileItem(file);
		MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
		return multipartFile;
	}

	private static FileItem createFileItem(File file) {
		FileItemFactory factory = new DiskFileItemFactory(16, null);
		FileItem item = factory.createItem("textField", "text/plain", true, file.getName());
		int bytesRead = 0;
		byte[] buffer = new byte[8192];
		try {
			FileInputStream fis = new FileInputStream(file);
			OutputStream os = item.getOutputStream();
			while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) {
				os.write(buffer, 0, bytesRead);
			}
			os.close();
			fis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return item;
	}


	/**
	 * MultipartFile转File
	 */
	public static File multipartFileToFile(MultipartFile multipartFile) {
		// 获取文件名
		String fileName = multipartFile.getOriginalFilename();
		// 获取文件后缀
		String prefix = "." + getExtensionName(fileName);
		File file = null;
		try {
			// 用uuid作为文件名，防止生成的临时文件重复
			file = File.createTempFile(IdUtil.simpleUUID(), prefix);
			// MultipartFile to File
			multipartFile.transferTo(file);
		} catch (IOException e) {
			log.error(e.getMessage(), e);
		}
		return file;
	}

	/**
	 * inputStream 转 File
	 */
	public static File inputStreamToFile(InputStream ins, String name) throws Exception {
		File file = new File(SYS_TEM_DIR + name);
		if (file.exists()) {
			return file;
		}
		OutputStream os = new FileOutputStream(file);
		int bytesRead;
		int len = 8192;
		byte[] buffer = new byte[len];
		while ((bytesRead = ins.read(buffer, 0, len)) != -1) {
			os.write(buffer, 0, bytesRead);
		}
		os.close();
		ins.close();
		return file;
	}



	/**
	 * Java文件操作 获取不带扩展名的文件名
	 */
	private static String getFileNameNoEx(String filename) {
		if ((filename != null) && (filename.length() > 0)) {
			int dot = filename.lastIndexOf('.');
			if ((dot > -1) && (dot < (filename.length()))) {
				return filename.substring(0, dot);
			}
		}
		return filename;
	}

	/**
	 * 获取文件扩展名，不带 .
	 */
	private static String getExtensionName(String filename) {
		if ((filename != null) && (filename.length() > 0)) {
			int dot = filename.lastIndexOf('.');
			if ((dot > -1) && (dot < (filename.length() - 1))) {
				return filename.substring(dot + 1);
			}
		}
		return filename;
	}

	/**
	 * 获取文件的后缀
	 *
	 * @param file 文件
	 * @return
	 */
	public static String getFileSuffix(String file) {
		if (file == null) {
			return null;
		}
		int extIndex = file.lastIndexOf(EXTENSION_SEPARATOR);
		if (extIndex == -1) {
			return null;
		}
		int folderIndex = file.lastIndexOf(FOLDER_SEPARATOR);
		if (folderIndex > extIndex) {
			return null;
		}
		return file.substring(extIndex + 1);
	}

	/**
	 * 获取文件名，构建结构为 prefix + yyyyMMddHH24mmss + 10位随机数 + suffix + .type
	 *
	 * @param type   文件类型
	 * @param prefix 前缀
	 * @param suffix 后缀
	 * @return
	 */
	public static String getFileName(String type, String prefix, String suffix) {
		//当前时间
		String date = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
		//10位随机数
		String random = String.valueOf(RANDOM_NUM);
		//返回文件名  
		return prefix + date + random + suffix + "." + type;
	}

	/**
	 * 获取文件名，文件名构成:当前时间 + 10位随机数 + .type
	 *
	 * @param type 文件类型
	 * @return
	 */
	public static String getFileName(String type) {
		return getFileName(type, "", "");
	}

	/**
	 * 获取文件名，文件构成：当前时间 + 10位随机数
	 *
	 * @return
	 */
	public static String getFileName() {
		//当前时间
		String date = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
		//10位随机数
		String random = String.valueOf(RANDOM_NUM);
		//返回文件名
		return date + random;
	}

	/**
	 * 文件重命名
	 *
	 * @param oldPath 老文件
	 * @param newPath 新文件
	 */
	public static boolean renameDir(String oldPath, String newPath) {
		// 文件或目录
		File oldFile = new File(oldPath);
		// 文件或目录
		File newFile = new File(newPath);
		// 重命名
		return oldFile.renameTo(newFile);
	}


	/**
	 * 删除所有文件，包括文件夹
	 *
	 * @param dirpath
	 */
	public static void deleteAll(String dirpath) {
		File path = new File(dirpath);
		try {
			if (!path.exists()) {
				// 目录不存在退出
				return;
			}
			// 如果是文件删除
			if (path.isFile()) {
				path.delete();
				return;
			}
			// 如果目录中有文件递归删除文件
			File[] files = path.listFiles();
			for (int i = 0; i < files.length; i++) {
				deleteAll(files[i].getAbsolutePath());
			}
			path.delete();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 复制文件或者文件夹
	 *
	 * @param inputFile   源文件
	 * @param outputFile  目的文件
	 * @param isOverWrite 是否覆盖文件
	 * @throws IOException
	 */
	public static void copy(File inputFile, File outputFile, boolean isOverWrite)
			throws IOException {
		if (!inputFile.exists()) {
			throw new RuntimeException(inputFile.getPath() + "源目录不存在!");
		}
		copyPri(inputFile, outputFile, isOverWrite);
	}

	/**
	 * 复制文件或者文件夹
	 *
	 * @param inputFile   源文件
	 * @param outputFile  目的文件
	 * @param isOverWrite 是否覆盖文件
	 * @throws IOException
	 */
	private static void copyPri(File inputFile, File outputFile, boolean isOverWrite) throws IOException {
		//文件
		if (inputFile.isFile()) {
			copySimpleFile(inputFile, outputFile, isOverWrite);
		} else {
			//文件夹
			if (!outputFile.exists()) {
				outputFile.mkdirs();
			}
			// 循环子文件夹
			for (File child : inputFile.listFiles()) {
				copy(child, new File(outputFile.getPath() + "/" + child.getName()), isOverWrite);
			}
		}
	}

	/**
	 * 复制单个文件
	 *
	 * @param inputFile   源文件
	 * @param outputFile  目的文件
	 * @param isOverWrite 是否覆盖
	 * @throws IOException
	 */
	private static void copySimpleFile(File inputFile, File outputFile,
									   boolean isOverWrite) throws IOException {
		if (outputFile.exists()) {
			//可以覆盖
			if (isOverWrite) {
				if (!outputFile.delete()) {
					throw new RuntimeException(outputFile.getPath() + "无法覆盖！");
				}
			} else {
				// 不允许覆盖
				return;
			}
		}
		InputStream in = new FileInputStream(inputFile);
		OutputStream out = new FileOutputStream(outputFile);
		byte[] buffer = new byte[1024];
		int read = 0;
		while ((read = in.read(buffer)) != -1) {
			out.write(buffer, 0, read);
		}
		in.close();
		out.close();
	}


	/**
	 * 判断两个文件是否相同
	 */
	public static boolean checkFileMD5(File file1, File file2) {
		String img1Md5 = getFileMD5(file1);
		String img2Md5 = getFileMD5(file2);
		return img1Md5.equals(img2Md5);
	}

	/**
	 * 获取文件的MD5
	 *
	 * @param file 文件
	 * @return
	 */
	public static String getFileMD5(File file) {
		if (!file.exists() || !file.isFile()) {
			return null;
		}
		MessageDigest digest = null;
		FileInputStream in = null;
		byte buffer[] = new byte[1024];
		int len;
		try {
			digest = MessageDigest.getInstance("MD5");
			in = new FileInputStream(file);
			while ((len = in.read(buffer, 0, 1024)) != -1) {
				digest.update(buffer, 0, len);
			}
			in.close();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		BigInteger bigInt = new BigInteger(1, digest.digest());
		return bigInt.toString(16);
	}

	/**
	 * 递归读取文件夹下的所有文件
	 *
	 * @param path
	 */
	public static void readFileInDir(String path, List<File> fileList) {
		File f = new File(path);
		//得到文件下的所有文件
		File[] files = f.listFiles();

		for (File file : files) {
			if (file.isDirectory()) {
				readFileInDir(file.getAbsolutePath(), fileList);
			} else {
				fileList.add(file);
			}
		}
	}

	/**
	 * 获取指定文件的大小
	 *
	 * @param file
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("resource")
	public static long getFileSize(File file) throws Exception {
		long size = 0;
		if (file.exists()) {
			FileInputStream fis = null;
			fis = new FileInputStream(file);
			size = fis.available();
		} else {
			file.createNewFile();
		}
		return size;
	}


	/**
	 * 获取文件夹大小
	 */
	public static void getFileSize2(File file) {
		//方法一：使用 length
		System.out.println("文件名称大小" + file.length());

		//方法二:Apache Commons IO
		long size = org.apache.commons.io.FileUtils.sizeOf(file);

		//方法三:文件大小转换
		org.apache.commons.io.FileUtils.byteCountToDisplaySize(size);
	}

	/**
	 * 文件大小转换
	 */
	public static String getSize(long size) {
		String resultSize;
		if (size / GB >= 1) {
			//如果当前Byte的值大于等于1GB
			resultSize = DF.format(size / (float) GB) + "GB   ";
		} else if (size / MB >= 1) {
			//如果当前Byte的值大于等于1MB
			resultSize = DF.format(size / (float) MB) + "MB   ";
		} else if (size / KB >= 1) {
			//如果当前Byte的值大于等于1KB
			resultSize = DF.format(size / (float) KB) + "KB   ";
		} else {
			resultSize = size + "B   ";
		}
		return resultSize;
	}

	/**
	 * @param filePath 指定的文件路径
	 * @param isNew    true：新建、false：不新建
	 * @return 存在返回TRUE，不存在返回FALSE
	 * @desc:判断指定路径是否存在，如果不存在，根据参数决定是否新建
	 */
	public static boolean isExist(String filePath, boolean isNew) {
		File file = new File(filePath);
		if (!file.exists() && isNew) {
			//新建文件路径
			return file.mkdirs();
		}
		return false;
	}


	/**
	 * 将文件名解析成文件的上传路径
	 */
	public static File upload(MultipartFile file, String filePath) {
		Date date = new Date();
		SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS");
		String name = getFileNameNoEx(file.getOriginalFilename());
		String suffix = getExtensionName(file.getOriginalFilename());
		String nowStr = "-" + format.format(date);
		try {
			String fileName = name + nowStr + "." + suffix;
			String path = filePath + fileName;
			// getCanonicalFile 可解析正确各种路径
			File dest = new File(path).getCanonicalFile();
			// 检测是否存在目录
			if (!dest.getParentFile().exists()) {
				if (!dest.getParentFile().mkdirs()) {
					System.out.println("was not successful.");
				}
			}
			// 文件写入
			file.transferTo(dest);
			return dest;
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		return null;
	}


	/**
	 * 文件上传
	 *
	 * @param file
	 * @return
	 */
	public static String uploadFile(MultipartFile file, String filePath) {
		//生成唯一路径
		UUID uuid = UUID.randomUUID();
		String dateName = DateTimeUtils.DateToString(new Date(), DateStyle.YYYY_MM);
		String fileName = uuid.toString().replaceAll("-", "") + "_-_-_" + file.getOriginalFilename();
		String year = String.valueOf(DateTimeUtils.getYear(dateName));
		int month = DateTimeUtils.getMonth(dateName);
		String monthStr = month < 10 ? "0" + month : month + "";
		String dir = year + File.separator + monthStr;
		StringBuilder sb = new StringBuilder().append(File.separator).append(dir).append(File.separator).append(fileName);
		File targetFile = new File(filePath, sb.toString());
		try {
			File fileParent = targetFile.getParentFile();
			if (!fileParent.exists()) {
				fileParent.mkdirs();
			}
			boolean flag = targetFile.createNewFile();
			if (flag) {
				FileCopyUtils.copy(file.getBytes(), targetFile);
			}
		} catch (IOException e) {
			log.error("文件上传失败", e);
		}
		return sb.toString().replaceAll("\\\\", "/");
	}
}
